/*
 * Copyright 2007-2008  Lukasz Laszko lukaszlaszko@gmail.com
 * 
 * Permission to use, copy, modify, and distribute this software
 * and its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice appear in all
 * copies and that both that the copyright notice and this
 * permission notice and warranty disclaimer appear in supporting
 * documentation, and that the name of the author not be used in
 * advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 * 
 * The author disclaim all warranties with regard to this
 * software, including all implied warranties of merchantability
 * and fitness.  In no event shall the author be liable for any
 * special, indirect or consequential damages or any damages
 * whatsoever resulting from loss of use, data or profits, whether
 * in an action of contract, negligence or other tortious action,
 * arising out of or in connection with the use or performance of
 * this software. 
 */
package org.pg.biomedics.asr.service;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.UUID;

import org.pg.biomedics.asr.model.Task;
import org.pg.biomedics.asr.model.Result;
import org.pg.biomedics.asr.model.TaskHistoryEntry;
import org.pg.biomedics.asr.model.code.TaskStateCodes;
import org.pg.biomedics.asr.model.code.TaskPriorityCodes;
import org.pg.biomedics.asr.data.TaskManager;
import org.pg.biomedics.asr.data.ResultManager;
import org.pg.biomedics.asr.data.DataAccessException;
import org.pg.biomedics.asr.config.ConfigurationManager;
import org.pg.biomedics.asr.config.ConfigurationException;

/**
 * This service conatins common application business logic methods
 * @author Lukasz Laszko
 */
public class CommonService 
{
    // <editor-fold desc="Fields">
    
    private String mediaWorkDir;
    private TaskManager taskManager;
    private ResultManager resultManager;
    
    // </editor-fold>
    
    // <editor-fold desc="Constructors">
    
    /**
     * Creates instance of CommonService
     * @throws org.pg.biomedics.asr.logic.ServiceException
     */
    public CommonService() throws ServiceException
    {
        try
        {
            ConfigurationManager configuration = new ConfigurationManager();
            
            this.mediaWorkDir = configuration.getMediaWorkDir();
            this.taskManager = new TaskManager(configuration.getSqlMapConfig());
            this.resultManager = new ResultManager(configuration.getSqlMapConfig());
        }
        catch (ConfigurationException cex)
        {
            throw new ServiceException("An error occured during configuration access", cex);
        }
        catch (DataAccessException daex)
        {
            throw new ServiceException("An error occured during data access initialization", daex);
        }
    }
    
    // </editor-fold>
    
    // <editor-fold desc="Methods">
    
    /**
     * Puts new ASR task into task queue
     * @param name task name
     * @param type task type
     * @param priority task priority code
     * @param media attached task media data
     * @return newly enqued task 
     * @throws org.pg.biomedics.asr.logic.ServiceException
     */
    public Task enqueueTask(String name, String type, String priority, byte[] media)
            throws ServiceException
    {
        File mediaFile = null;
        
        try
        {
            // create new task
            Task task = new Task();
            task.setName(name);
            task.setUniqueId(UUID.randomUUID().toString());
            task.setTypeCode(type);
            task.setPriorityCode(priority);
            
            // save media in temporary location
            mediaFile = File.createTempFile(UUID.randomUUID().toString(), ".wav", new File(this.mediaWorkDir)).getAbsoluteFile();
            task.setMediaLocation(mediaFile.getPath());
            
            FileOutputStream outputStream = new FileOutputStream(mediaFile);
            outputStream.write(media);            
            outputStream.close();
            
            // store info in database                         
            this.taskManager.insertTask(task);
            this.taskManager.SetTaskState(task, TaskStateCodes.WAITING);
            
            task.setStateCode(TaskStateCodes.WAITING);
            return task;
        }
        catch (IOException ex)
        {
            throw new ServiceException(String.format("Can't enqueue task ( name=%1, type=%2, priority=%3 ), problem with saving work media file", name, type, priority), ex);
        }
        catch (DataAccessException ex)
        {
            if (mediaFile != null)
                mediaFile.delete();
                
            throw new ServiceException(String.format("Can't enqueue task ( name=%1, type=%2, priority=%3 ) data access error", name, type, priority), ex);
        }
    }
    
    /**
     * Gets task by task unique id
     * @param taskUniqueId task unique id (GUID) 
     * @return task instance or null task doesn not exist
     * @throws org.pg.biomedics.asr.service.ServiceException
     */
    public Task getTask(String taskUniqueId) throws ServiceException
    {
        // input params validation
        if (taskUniqueId == null)
            throw new ServiceException("Can't check task status, task unique id can't be null");
        else if (taskUniqueId.equals(""))
            throw new ServiceException("Can't check task status, task unique id can't be empty string");
        
        try
        {
            return this.taskManager.getTask(taskUniqueId);
        }
        catch (DataAccessException ex)
        {
            throw new ServiceException(String.format("Can't check task status for unique id %1", taskUniqueId), ex);
        }
    }
    
    /**
     * Checks status of given task
     * @param taskUniqueId task unique id (GUID)
     * @return status code
     * @throws org.pg.biomedics.asr.logic.ServiceException
     */
    public String checkTaskStatus(String taskUniqueId) throws ServiceException
    {
        // input params validation
        if (taskUniqueId == null)
            throw new ServiceException("Can't check task status, task unique id can't be null");
        else if (taskUniqueId.equals(""))
            throw new ServiceException("Can't check task status, task unique id can't be empty string");
        
        try
        {
            Task task = this.taskManager.getTask(taskUniqueId);
            return task.getStateCode();
        }
        catch (DataAccessException ex)
        {
            throw new ServiceException(String.format("Can't check task status for unique id %1", taskUniqueId), ex);
        }
    }   
    
    /**
     * Gets all tasks in input queue (in WAITING status)
     * @return list of waiting tasks
     * @throws org.pg.biomedics.asr.logic.ServiceException
     */
    public Task[] getWaitingTasks() throws ServiceException
    {
        try
        {
            return this.taskManager.getTasks(TaskStateCodes.WAITING);
        }
        catch (DataAccessException ex)
        {
            throw new ServiceException("Can't get waiting tasks, data access error", ex);
        }
    }
    
    /**
     * Gets list of currently executed tasks
     * @return list of tasks
     * @throws org.pg.biomedics.asr.logic.ServiceException
     */
    public Task[] getPendingTasks() throws ServiceException
    {
        try
        {
            return this.taskManager.getTasks(TaskStateCodes.PENDING);
        }
        catch (DataAccessException ex)
        {
            throw new ServiceException("Can't get pending tasks, data access error", ex);
        }
    }
    
    /**
     * Gets list of all waiting and pending tasks sorted by priority
     * @return list of enqueued tasks
     * @throws org.pg.biomedics.asr.service.ServiceException
     */
    public Task[] getEnqueuedTasks() throws ServiceException
    {
        try
        {
             // get tasks from database
            Task[] pendingTasks = this.taskManager.getTasks(TaskStateCodes.PENDING);
            Task[] waitingTasks = this.taskManager.getTasks(TaskStateCodes.WAITING);

            Task[] allTasks = new Task[pendingTasks.length + waitingTasks.length]; 
            int index = 0;

            // sort pending tasks        
            for (int i = 0; i < pendingTasks.length; i++)
                if (pendingTasks[i].getPriorityCode() != null)
                    if (pendingTasks[i].getPriorityCode().equals(TaskPriorityCodes.HIGH))
                        allTasks[index++] = pendingTasks[i];

            for (int i = 0; i < pendingTasks.length; i++)
                if (pendingTasks[i].getPriorityCode() != null)
                    if (pendingTasks[i].getPriorityCode().equals(TaskPriorityCodes.NORMAL))
                        allTasks[index++] = pendingTasks[i];

            for (int i = 0; i < pendingTasks.length; i++)
                if (pendingTasks[i].getPriorityCode() != null)
                    if (pendingTasks[i].getPriorityCode().equals(TaskPriorityCodes.LOW))
                        allTasks[index++] = pendingTasks[i];

            // sort waiting tasks
            for (int i = 0; i < waitingTasks.length; i++)
                if (waitingTasks[i].getPriorityCode() != null)
                    if (waitingTasks[i].getPriorityCode().equals(TaskPriorityCodes.HIGH))
                        allTasks[index++] = waitingTasks[i];

            for (int i = 0; i < waitingTasks.length; i++)
                if (waitingTasks[i].getPriorityCode() != null)
                    if (waitingTasks[i].getPriorityCode().equals(TaskPriorityCodes.NORMAL))
                        allTasks[index++] = waitingTasks[i];

            for (int i = 0; i < waitingTasks.length; i++)
                if (waitingTasks[i].getPriorityCode() != null)
                    if (waitingTasks[i].getPriorityCode().equals(TaskPriorityCodes.LOW))
                        allTasks[index++] = waitingTasks[i];

            return allTasks;
        }
        catch (Exception ex)
        {
            throw new ServiceException("Can't get enqueued tasks", ex);
        }
    }
    
    /**
     * Get list of resulting tasks. This list consists of tasks which retruned 
     * a result but wasn't finalized with finish method
     * @return list of resulting tasks
     * @throws org.pg.biomedics.asr.logic.ServiceException
     */
    public Task[] getResultingTasks() throws ServiceException
    {
        try
        {
            // get both succeeded and failed tasks
            Task[] succeededTasks = this.taskManager.getTasks(TaskStateCodes.SUCCESS);
            Task[] failedTasks = this.taskManager.getTasks(TaskStateCodes.FAILED);
            
            // combine results
            Task[] resultingTasks = new Task[succeededTasks.length + failedTasks.length];
            for (int i = 0; i < succeededTasks.length; i++)
                resultingTasks[i] = succeededTasks[i];
            for (int i = 0; i < failedTasks.length; i++)
                resultingTasks[succeededTasks.length + i] = failedTasks[i];
            
            return resultingTasks;
        }
        catch (DataAccessException ex)
        {
            throw new ServiceException("Can't get waiting tasks, data access error", ex);
        }
    }
    
    /**
     * Marks resulting task as finished. Only tasks in resulting state can be finished
     * @param task
     * @return finished task
     * @throws org.pg.biomedics.asr.logic.ServiceException
     */
    public Task finishTask(Task task) throws ServiceException
    {
        // input params validation
        if (task == null)
            throw new ServiceException("Can't finish task, task can't be null");
        else if (task.getId() == 0)
            throw new ServiceException("Can't finish task, task must be saved first");
        else if ((!TaskStateCodes.SUCCESS.equals(task.getStateCode())) && (!TaskStateCodes.FAILED.equals(task.getStateCode())))
            throw new ServiceException(String.format("Can't finish task, only resulting tasks can be finished task %s state is %s", 
                                                     new Integer(task.getId()).toString(), task.getStateCode()));
            
        try
        {
            // set task state code
            this.taskManager.SetTaskState(task, TaskStateCodes.FINISHED);
            
            // remove work media file
            File mediaFile = new File(task.getMediaLocation());
            if (mediaFile.exists())
                mediaFile.delete();
                
            task.setStateCode(TaskStateCodes.FINISHED);
            return task;
        }
        catch (DataAccessException ex)
        {
            throw new ServiceException(String.format("Can't finish task %1, data access error", 
                                                     new Integer(task.getId()).toString()));
        }
    }
    
    /**
     * Cancels waiting or resulting task
     * @param task
     * @return 
     * @throws org.pg.biomedics.asr.logic.ServiceException
     */
    public Task cancelTask(Task task) throws ServiceException
    {
        // input params validation
        if (task == null)
            throw new ServiceException("Can't cancel task, task can't be null");
        else if (task.getId() == 0)
            throw new ServiceException("Can't cancel task, task must be saved first");
        else if ((!TaskStateCodes.SUCCESS.equals(task.getStateCode())) && 
                 (!TaskStateCodes.FAILED.equals(task.getStateCode())) &&
                 (!TaskStateCodes.WAITING.equals(task.getStateCode())))
            throw new ServiceException(String.format("Can't cancel task, only resulting or waiting tasks can be canceled task %1 state is %2", 
                                                     new Integer(task.getId()).toString(), task.getStateCode()));
            
        try
        {
            // set task cancel state code
            this.taskManager.SetTaskState(task, TaskStateCodes.CANCELED);
            
            // remove work media file
            File mediaFile = new File(task.getMediaLocation());
            if (mediaFile.exists())
                mediaFile.delete();
            
            task.setStateCode(TaskStateCodes.CANCELED);
            return task;
        }
        catch (DataAccessException ex)
        {
            throw new ServiceException(String.format("Can't cancel task %1, data access error", 
                                                     new Integer(task.getId()).toString()));
        }
    }
    
    /**
     * Gets task execution results
     * @param task task 
     * @return list of task results
     * @throws org.pg.biomedics.asr.logic.ServiceException
     */
    public Result[] getResults(Task task) throws ServiceException
    {
        // input param validation
        if (task == null)
            throw new ServiceException("Can't get task results, task can't be null");
        else if (task.getId() == 0)
            throw new ServiceException("Can't get task results, task must be saved first");
        
        try
        {
            return this.resultManager.getResults(task);
        }
        catch (DataAccessException ex)
        {
            throw new ServiceException(String.format("Can't get results for task %1, data access error", 
                                                     new Integer(task.getId()).toString()));
        }
    }
    
    /**
     * Gets task execution results by task unique id
     * @param taskUniqueId task unique id
     * @return list of task results
     * @throws org.pg.biomedics.asr.service.ServiceException
     */
    public Result[] getResults(String taskUniqueId) throws ServiceException
    {
        try
        {
            Task task = this.taskManager.getTask(taskUniqueId);            
            if (task == null)
                throw new ServiceException(String.format("Task %1 does not exist", taskUniqueId));
            
            return this.getResults(task);
        }
        catch (Exception ex)
        {
            throw new ServiceException(String.format("Can't get results for task with id %1", taskUniqueId), ex);
        }
    }
    
    /**
     * Gets task state history
     * @param task
     * @return
     * @throws org.pg.biomedics.asr.logic.ServiceException
     */
    public TaskHistoryEntry[] getHistory(Task task) throws ServiceException
    {
        // input param validation
        if (task == null)
            throw new ServiceException("Can't get task history, task can't be null");
        else if (task.getId() == 0)
            throw new ServiceException("Can't get task history, task must be saved first");
        
        try
        {
            return this.taskManager.getTaskHistory(task);
        }
        catch (DataAccessException ex)
        {
            throw new ServiceException(String.format("Can't get history for task %1, data access error", 
                                                     new Integer(task.getId()).toString()));
        }
    }
    
    // </editor-fold>
}
